﻿using UnityEngine;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using Collider = Unity.Physics.Collider;
using RaycastHit = Unity.Physics.RaycastHit;
using Unity.Collections;

namespace Jinndev.Physics {

    /// <summary>
    /// 
    /// </summary>
    public static class PhysicsUtil {

        private static BuildPhysicsWorld buildPhysicsWorld;
        public static BuildPhysicsWorld BuildPhysicsWorld { 
            get {
                if (buildPhysicsWorld == null) {
                    buildPhysicsWorld = World.DefaultGameObjectInjectionWorld.GetOrCreateSystem<BuildPhysicsWorld>();
                }
                return buildPhysicsWorld;
            }
            set {
                buildPhysicsWorld = value;
            }
        }

        public static PhysicsWorld PhysicsWorld {
            get {
                return BuildPhysicsWorld.PhysicsWorld;
            }
        }

        public static EntityManager EntityManager {
            get {
                return BuildPhysicsWorld.EntityManager;
            }
        }

        private static Entity PhysicsStepEntity = Entity.Null;

        public static void SetPhysicsStep(PhysicsStep physicsStep) {
            if(PhysicsStepEntity == Entity.Null) {
                PhysicsStepEntity = EntityManager.CreateEntity(typeof(PhysicsStep));
            }
            EntityManager.SetComponentData(PhysicsStepEntity, physicsStep);
        }

        public static void SetGravity(float3 gravity) {
            if (PhysicsStepEntity == Entity.Null) {
                PhysicsStepEntity = EntityManager.CreateEntity();
                EntityManager.AddComponentData(PhysicsStepEntity, PhysicsStep.Default);
#if UNITY_EDITOR
                EntityManager.SetName(PhysicsStepEntity, "PhysicsStep");
#endif
            }
            PhysicsStep physicsStep = EntityManager.GetComponentData<PhysicsStep>(PhysicsStepEntity);
            physicsStep.Gravity = gravity;
            EntityManager.SetComponentData(PhysicsStepEntity, physicsStep);
        }

        public static void Dispose() {
            BuildPhysicsWorld = null;
            PhysicsStepEntity = Entity.Null;
        }

        public static BlobAssetReference<Collider> CreateQuadCollider(float3 size, float3 center, CollisionFilter filter) {
            float3 vertex0 = size * new float3(-0.5f, -0.5f, 0) + center * size;
            float3 vertex1 = size * new float3(-0.5f, 0.5f, 0) + center * size;
            float3 vertex2 = size * new float3(0.5f, 0.5f, 0) + center * size;
            float3 vertex3 = size * new float3(0.5f, -0.5f, 0) + center * size;
            return PolygonCollider.CreateQuad(vertex0, vertex1, vertex2, vertex3, filter);
        }

        public static BlobAssetReference<Collider> CreateBoxCollider(float3 size, float3 center, CollisionFilter filter) {
            return Unity.Physics.BoxCollider.Create(new BoxGeometry() {
                Center = center,
                Orientation = quaternion.identity,
                Size = size
            }, filter);
        }


        [System.Obsolete]
        public static CollisionFilter GetBelongsToFilter(uint belongsTo) {
            return new CollisionFilter {
                BelongsTo = belongsTo,
                CollidesWith = PhysicsConst.AllMask,
                GroupIndex = 0
            };
        }

        [System.Obsolete]
        public static CollisionFilter GetCollidesWithFilter(uint collidesWith) {
            return new CollisionFilter {
                BelongsTo = PhysicsConst.AllMask,
                CollidesWith = collidesWith,
                GroupIndex = 0
            };
        }

        public static CollisionFilter GetFilter(uint belongsTo, uint collidesWith) {
            return new CollisionFilter {
                BelongsTo = belongsTo,
                CollidesWith = collidesWith,
                GroupIndex = 0
            };
        }

        public static void SetBelongsTo(BlobAssetReference<Collider> collider, uint mask) {
            if (!collider.IsCreated) {
                return;
            }
            collider.Value.Filter = GetBelongsToFilter(mask);
            //Collider collider = collider.Value;
            //collider.Value = collider;
        }

        public static bool Raycast(PhysicsWorld physicsWorld, float3 start, float3 end, CollisionFilter filter, out RaycastResult result) {
            RaycastInput input = new RaycastInput() {
                Start = start,
                End = end,
                Filter = filter
            };
            if (physicsWorld.CollisionWorld.CastRay(input, out RaycastHit hit)) {
                RigidBody rigidBody = physicsWorld.Bodies[hit.RigidBodyIndex];
                result = new RaycastResult() {
                    hit = hit,
                    rigidBody = rigidBody,
                    entity = rigidBody.Entity
                };
                return result.entity != Entity.Null;
            }
            else {
                result = default;
                return false;
            }
        }

        public static bool RaycastAll(PhysicsWorld physicsWorld, float3 start, float3 end, CollisionFilter filter, ref NativeList<RaycastResult> results) {
            RaycastInput input = new RaycastInput() {
                Start = start,
                End = end,
                Filter = filter
            };

            NativeList<RaycastHit> allHits = new NativeList<RaycastHit>(Allocator.TempJob);
            bool success = physicsWorld.CollisionWorld.CastRay(input, ref allHits);
            if (success) {
                foreach (RaycastHit hit in allHits) {
                    RigidBody rigidBody = physicsWorld.Bodies[hit.RigidBodyIndex];
                    results.Add(new RaycastResult() {
                        hit = hit,
                        rigidBody = rigidBody,
                        entity = rigidBody.Entity
                    });
                }
            }
            allHits.Dispose();
            return success;
        }

        /// <summary>
        /// 得到碰撞器指定方向的边缘坐标
        /// </summary>
        /// <param name="physicsCollider"></param>
        /// <param name="direction"></param>
        /// <param name="position"></param>
        /// <returns></returns>
        public static float2 Get2DColliderEdgePosition(PhysicsCollider physicsCollider, Vector2 direction, float2 position) {
            if (!physicsCollider.Value.IsCreated) {
                return position;
            }
            Aabb aabb = physicsCollider.Value.Value.CalculateAabb();

            if (direction == Vector2.down) {
                position.y += aabb.Min.y;
            }
            else if (direction == Vector2.up) {
                position.y += aabb.Max.y;
            }
            else if (direction == Vector2.left) {
                position.x += aabb.Min.x;
            }
            else if (direction == Vector2.right) {
                position.x += aabb.Max.x;
            }
            else {
                Debug.LogError($"Invalid direction: {direction}");
            }
            
            return position;
        }

        public static bool HasMask(uint value, uint mask) {
            return (value & mask) == mask;
        }

        public static uint AddMask(ref uint value, uint mask) {
            return value ^= mask;
        }

        public static uint RemoveMask(ref uint value, uint mask) {
            return value |= mask;
        }


        //        public static Entity CreateLockedZAxisJointEntity(EntityManager entityManager, Entity entity) {
        //            var worldFromA = new RigidTransform(
        //                entityManager.GetComponentData<Rotation>(entity).Value,
        //                entityManager.GetComponentData<Translation>(entity).Value
        //            );
        //            RigidTransform bFromA = math.mul(math.inverse(RigidTransform.identity), worldFromA);
        //            BlobAssetReference<JointData> jointData = CreateLockedZAxisJoint(bFromA);

        //            var componentData = new PhysicsJoint {
        //                JointData = jointData,
        //                EntityA = entity,
        //                EnableCollision = 1,
        //            };

        //            Entity jointEntity = entityManager.CreateEntity(typeof(PhysicsJoint));
        //            entityManager.AddComponentData(jointEntity, componentData);

        //#if UNITY_EDITOR
        //            entityManager.SetName(jointEntity, $"Joining {entityManager.GetName(entity)} + PhysicsWorld");
        //#endif

        //            return jointEntity;
        //        }

        //        public static BlobAssetReference<JointData> CreateLockedZAxisJoint(RigidTransform offset) {
        //            NativeArray<PhysicsConstraint> PhysicsConstraints = new NativeArray<PhysicsConstraint>(2, Allocator.Temp);
        //            PhysicsConstraints[0] = new PhysicsConstraint {
        //                PhysicsConstrainedAxes = new bool3(false, false, true),
        //                Type = PhysicsConstraintType.Linear,
        //                Min = 0,
        //                Max = 0,
        //                SpringFrequency = PhysicsConstraint.DefaultSpringFrequency,
        //                SpringDamping = PhysicsConstraint.DefaultSpringDamping
        //            };
        //            PhysicsConstraints[1] = new PhysicsConstraint {
        //                PhysicsConstrainedAxes = new bool3(false, false, true),
        //                Type = PhysicsConstraintType.Angular,
        //                Min = 0,
        //                Max = 0,
        //                SpringFrequency = PhysicsConstraint.DefaultSpringFrequency,
        //                SpringDamping = PhysicsConstraint.DefaultSpringDamping
        //            };

        //            JointFrame frameA = new JointFrame(new RigidTransform(float3x3.identity, float3.zero));
        //            JointFrame frameB = new JointFrame(offset);

        //            BlobAssetReference<JointData>  data = JointData.Create(frameA, frameB, PhysicsConstraints);
        //            PhysicsConstraints.Dispose();
        //            return data;
        //            //return JointData.Create(
        //            //    new Math.MTransform(float3x3.identity, float3.zero),
        //            //    new Math.MTransform(offset),
        //            //    PhysicsConstraints
        //            //);
        //        }

    }
}
